﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection.Emit;
using System.Collections;
using System.Reflection;
using System.Threading;

namespace Evaluant.Uss.Common
{
    /// <summary>
    /// Generates IActivator classes implementations dynamically
    /// </summary>
    public class ActivatorFactory
    {
        private static Dictionary<Type, IActivator> _ActivatorCache = new Dictionary<Type, IActivator>(20);

        public static IActivator CreateActivator(Type typeToActivate)
        {
            if (_ActivatorCache.ContainsKey(typeToActivate))
            {
                return _ActivatorCache[typeToActivate];
            }

            // Create an assembly name
            AssemblyName assemblyName = new AssemblyName();
            assemblyName.Name = typeToActivate.FullName;

            // Create a new assembly with one module
            AssemblyBuilder newAssembly = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
            ModuleBuilder moduleBuilder = newAssembly.DefineDynamicModule(assemblyName.Name + ".dll");

            // Define a public class named "Activator" in the assembly.
            TypeBuilder typeBuilder = moduleBuilder.DefineType("Activator",
                TypeAttributes.Public | TypeAttributes.Class,
                typeof(object),
                new Type[] { typeof(IActivator) });

            MethodBuilder methodBuilder =
                typeBuilder.DefineMethod("IActivator.CreateInstance",
                MethodAttributes.HideBySig |
                MethodAttributes.NewSlot |
                MethodAttributes.Public |
                MethodAttributes.SpecialName |
                MethodAttributes.Final |
                MethodAttributes.Virtual,
                typeof(object), new Type[0]);

            ILGenerator gen = methodBuilder.GetILGenerator();

            ConstructorInfo[] ctors = typeToActivate.GetConstructors();

            if (ctors.Length == 0)
            {
                throw new ArgumentException("The type " + typeToActivate.FullName + " doesn't have a default constructor");
            }

            ConstructorInfo ctor = null;

            int index = 0;

            do
            {
                ctor = ctors[index++];
            } while (ctor.GetParameters().Length != 0);

            gen.Emit(OpCodes.Newobj, ctor);
            gen.Emit(OpCodes.Ret);

            typeBuilder.DefineMethodOverride(
                methodBuilder,
                typeof(IActivator).GetMethod("CreateInstance"));

            // Load the type
            Type type = typeBuilder.CreateType();

            IActivator activator = Activator.CreateInstance(type) as IActivator;

            lock (_ActivatorCache)
            {
                if (!_ActivatorCache.ContainsKey(typeToActivate))
                    _ActivatorCache.Add(typeToActivate, activator);
                else
                    return _ActivatorCache[typeToActivate];
            }

            // newAssembly.Save(assemblyName.Name + ".dll");

            return activator;
        }
    }
}
